'''
$LastChangedBy: csgillespie $
$LastChangedDate: 2007-06-06 11:08:06 +0000 (Wed, 06 Jun 2007) $
Colin Gillespie (c.gillespie@ncl.ac.uk)

Imported into the BASISTools function
'''

from CompartmentTree import CompartmentTree, CompartmentNode
SBML_DICTIONARY = {'Co':'Compartments', 'Sp':'Species', 'Pa':'Parameters', \
	'Re':'Reactions', 'Ev':'Events'}


class DeleteSBML:
	'''Used to delete elements from the SBML model. Called in BASISTools'''

	def __init__(self, model, sbml_parts, alter_dict, recursive=1):
		self.m = model
		self.sbml_parts = sbml_parts
		self.alter_dict = alter_dict
		self.deleteValues = {'Co':[], 'Sp':[], 'Re':[], 'Ev':[], 'Pa':[]}
		self.recursive = recursive
			
	def __delete(self, part):
		'''Using the deleteValues dictionary, we go through the model
		and delete the element
		
		part is either Co/Sp/Pa/Ev/Re and determines which bit of the model
		we should delete
		
		self.deleteValues contains the index of the element to be delete.
		For example, the first species
		'''
		list_of_name = 'getListOf' + SBML_DICTIONARY[part]
		for value in self.deleteValues[part]:
			k = getattr(self.m, list_of_name)().remove(value)
			del k
	
	def alterModel(self):
		'''The class can be used as d = Delete(model, sbml_parts, alter_dict)
		then 
		d.deleteCompartments()
		d.deleteSpecies()
		d.deleteReactions() etc
		
		This method just does all that for you
		'''
		for part in self.sbml_parts:
			getattr(self, 'delete'+part)()
		return 1
		
	def deleteCompartments(self):
		'''If a compartment is being delete then we check for dependencies, 
		i.e. outside. It also update the deleteValues dict
		'''
		if self.recursive:
			cp_tree = CompartmentTree()
			for co in self.m.getListOfCompartments():
				cp_tree.add(CompartmentNode(co.getId(), outside=co.getOutside()))
			self.alter_dict['Co'] = cp_tree.delCompartment(self.alter_dict['Co'])
		
		#Calculate the id values
		for i, co in enumerate(self.m.getListOfCompartments()):
			if co.getId() in self.alter_dict['Co']:
				self.deleteValues['Co'].insert(0, i)
		self.__delete('Co')
		return 1

	def deleteSpecies(self):
		'''If a compartment is being delete then that specie is added to alter_dict
		It also updates the deleteValues dict
		'''
		for i, sp in enumerate(self.m.getListOfSpecies()):
			if sp.getId() in self.alter_dict['Sp']:
				self.deleteValues['Sp'].insert(0, i)
			elif sp.getCompartment() in  self.alter_dict['Co'] and self.recursive:
				self.alter_dict['Sp'].append(sp.getId())
				self.deleteValues['Sp'].insert(0, i)
		self.__delete('Sp')
		return 1				

	def deleteParameters(self):	
		'''Deletes the parameters'''

		for i, par in enumerate(self.m.getListOfParameters()):
			if par.getId() in self.alter_dict['Pa']:
				self.deleteValues['Pa'].insert(0, i)
		self.__delete('Pa')
		return 1

	def deleteReactions(self):	
		'''If a reaction is being delete then we check for dependencies, 
		i.e. has a species/parameter been deleted
	
		It also update the deleteValues dict
		'''

		for i, reaction in enumerate(self.m.getListOfReactions()):
			if reaction.getId() in self.alter_dict['Re']:
				self.deleteValues['Re'].insert(0, i)
			elif self.recursive and self.__isInReaction(reaction):
				self.alter_dict['Re'].append(reaction.getId())
				self.deleteValues['Re'].insert(0, i)
			elif self.recursive and self.__isParameterInKinticLaw(reaction):
				self.alter_dict['Re'].append(reaction.getId())
				self.deleteValues['Re'].insert(0, i)
		self.__delete('Re')
		return 1
	
	def __isInReaction(self, reaction):
		'''determines if any reactant/product/modifier is in the delete Species list'''
		type_of = ['Reactants', 'Products', 'Modifiers']
		for type_ in type_of:
			for specie in getattr(reaction, 'getListOf' + type_)():
				if specie.getSpecies() in self.alter_dict['Sp']: 
					return 1
		return 0

	def __isParameterInKinticLaw(self, reaction):
		'''Calulates local parameters. 
		Returns 1 if parameter is in KL 
		Return 0 otherwise 
		'''
		if not reaction.isSetKineticLaw():
			return 0
		kin_law = reaction.getKineticLaw()
		
		if not kin_law.isSetFormula():
			return 0
		
		local_pars = [local_par.getId() for local_par in kin_law.getListOfParameters()]
		tree = kin_law.getMath()
		return self.__isInParameterMath(tree, local_pars)
		
	def __isInParameterMath(self, tree, local_pars):
		'''Called by __isParameterInKinticLaw
		Checks to see if there are any parameters that are being deleted
		are in the kinetic law.
		'''
		
		if tree is None: 
			return
		
		if tree.isName():
			if tree.getName() in local_pars: 
				return 0
			if tree.getName() in self.alter_dict['Pa']: 
				return 1
		
		if self.__isInParameterMath(tree.getLeftChild(), local_pars): 
			return 1
		if self.__isInParameterMath(tree.getRightChild(), local_pars): 
			return 1
		return 0

	def deleteEvents(self):	
		'''If an event is being delete then we check for dependencies, 
		i.e. has a species/parameter been deleted
		
		It also update the deleteValues dict
		'''

		for i, ev in enumerate(self.m.getListOfEvents()):
			if ev.getId() in self.alter_dict['Ev']:
				self.deleteValues['Ev'].insert(0, i)
			elif self.recursive and self.__isInEventMath(ev.getTrigger()):
				self.deleteValues['Ev'].insert(0, i)
				self.alter_dict['Ev'].append(ev.getId())
			elif self.recursive and self.__checkEventAssignments(ev):
				self.deleteValues['Ev'].insert(0, i)
				self.alter_dict['Ev'].append(ev.getId())
		self.__delete('Ev')
		return 1

	def __checkEventAssignments(self, ev):
		'''Checks event assignments and returns:
		1 if an EA contains a deleted parameter or species
		0 otherwise
		'''
		for assign in ev.getListOfEventAssignments():
			var = assign.getVariable()
			if var in  self.alter_dict['Sp'] or var in self.alter_dict['Pa']:
				return 1
			elif self.__isInEventMath(assign.getMath()):
				return 1	

	def __isInEventMath(self, tree):
		'''Checks the Math'''
		if tree is None: 
			return
		if tree.isName():
			if tree.getName() in self.alter_dict['Pa']: 
				return 1
			if tree.getName() in self.alter_dict['Sp']: 
				return 1

		if self.__isInEventMath(tree.getLeftChild()): 
			return 1
		if self.__isInEventMath(tree.getRightChild()): 
			return 1
		return 0















